/********************************************************************************
 * @file    oled_core.c
 * @author  jianqiang.xue
 * @version V1.0.0
 * @date    2023-07-17
 * @brief   支持多屏操作
 * 参考：https://www.cnblogs.com/Gimiracle/p/13520991.html
 * 笔记：
 * 1. 定义结构体传递 oled_cfg_t
 * 2. 在得到应答后，发送一个控制字节，由Co位和D/C位及尾部000000组成。
 * D7 D6   D5 D4 D3 D2 D1 D0
 * Co D/C# 0  0  0  0  0  0
 * a）Co位为0，则后续字节均为数据。D/C#位确定下一个数据字节作为命令或数据。
 * b）D/C#位设置为“0”，后续数据字节定义为命令。 D/C#位设置为“1” ，后续数据字节定义为数据，并存储在GDDRAM上。
 *   每次数据写入后，GDDRAM列地址指针将自动增加一个。
 ********************************************************************************/
/* Includes ------------------------------------------------------------------*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include "bsp_i2c.h"
/* Private includes ----------------------------------------------------------*/
#include "SSD1306.h"
#include "oled_core.h"
#if OS
#include "os_api.h"
#include "app_main.h"
#endif
/* Private define ------------------------------------------------------------*/
#define FUNC_CMD  0x00
#define FUNC_DATA 0x40
/* Private typedef -----------------------------------------------------------*/

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/

static uint8_t g_oled_state = false;

static const uint8_t g_cmd_oled_init_cfg[] = {
    FUNC_CMD, /*命令类*/
    0xAE,     /*关闭oled面板*/
    0x00,     /*设置低列地址*/
    0x10,     /*设置高列地址*/
    0x40,     /*设置起始行地址*/

    0x81, /*对比度设置，可设置亮度*/
    0XFF, /*对比度值0-255*/

    0XA1, /*设置分段/列映射     0xa0左右反置 0xa1正常*/
    0XC8, /*设置COM/行扫描方向   0xc0上下反置 0xc8正常*/
    0XA6, /*设置正常显示 0xa7逆显示*/

    0XA8, /*设置多路复用比率（1到64）*/
    0X3F, /*1/64负荷*/

    0XD3, /*设置显示偏移移位映射RAM计数器*/
    0x00, /*RAM计数器值：0x00~0x3F*/

    0XD5, /*设置显示分时比/振荡器频率*/
    0X80, /*设置分频比，将时钟设置为100帧/秒(默认值)*/

    0XD9, /*设置 预充电周期*/
    0XF1, /*预充电设为15时钟，放电设为1时钟*/

    0XDA, /*设置com引脚硬件配置*/
    0X12, /*默认值*/

    0XDB, /*设置 共通电位 (高)*/
    0X40, /*设置VCOM取消选择级别*/

    0X20, /*设置页面寻址模式（0x00/0x01/0x02）*/
    0X02, /*默认值*/

    0X8D, /*设置预充电 启用/禁用*/
    0X14, /*set(0x10) disable*/

    0XA4, /*A4h，X0 =0b：恢复 RAM内容的显示（RESET）*/
    0XA6, /*A6h, X[0]=0b:正常显示（RESET）*/
};

static const uint8_t g_cmd_oled_open_display[] = {
    FUNC_CMD, /*命令类*/
    0X8D,     /*设置 共通电位 (高)*/
    0X14,
    0XAF      /*显示开，正常模式 */
};

static const uint8_t g_cmd_oled_close_display[] = {
    FUNC_CMD, /*命令类*/
    0X8D,     /*设置 共通电位 (高)*/
    0X14,
    0XAE      /*显示关 （睡眠模式）*/
};

static const uint8_t g_cmd_oled_null[129] = {FUNC_DATA, 0x00};

//static uint8_t g_oled_data_buff[128] = {0};
/* Public function prototypes -----------------------------------------------*/

/**
 * @brief  oled初始化
 * @note   由于单片机上电初始化比OLED快，所以必须加上延迟，等待OLED上复位完成，约100-200ms.
 * @param  cfg: OLED屏幕配置
 * @retval 0--成功 1--失败
 */
uint8_t oled_init(const oled_cfg_t *cfg) {
#if OS
    main_msgqueue_t msg;
    msg.event = MAIN_EVENT_I2C0_C_IOCTL;
    msg.argv0 = cfg->dev_addr;
    msg.argv1 = 1;
    msg.data = (uint8_t*)g_cmd_oled_init_cfg;
    msg.len = sizeof(g_cmd_oled_init_cfg);
    return main_enqueue(&msg);
#else
    return bsp_i2c_write_nbyte(cfg->i2c_bus, cfg->dev_addr,
                               (uint8_t*)g_cmd_oled_init_cfg, sizeof(g_cmd_oled_init_cfg));
#endif
}

/**
 * @brief  得到当前状态
 * @retval 0--空闲 1--忙
 */
inline uint8_t oled_get_state(void) {
    return g_oled_state;
}

/**
 * @brief  清屏
 * @param  *cfg: OLED屏幕配置
 */
void oled_cls(const oled_cfg_t *cfg) {
    uint8_t data[] = {FUNC_CMD, 0xb0, 0x00, 0x10};
#if OS==0
    main_msgqueue_t msg;
#endif
    for (uint8_t i = 0; i < cfg->y_max / 8; i++) {
        data[1] = 0xb0 + i;
#if OS==0
        msg.argv0 = cfg->dev_addr;
        msg.argv1 = 1;

        msg.event = MAIN_EVENT_I2C0_IOCTL;
        msg.data = data;
        msg.len = sizeof(data);
        main_enqueue(&msg);

        msg.event = MAIN_EVENT_I2C0_C_IOCTL;
        msg.data = (uint8_t*)g_cmd_oled_null;
        msg.len = sizeof(g_cmd_oled_null);
        main_enqueue(&msg);
#else
        bsp_i2c_write_nbyte(cfg->i2c_bus, cfg->dev_addr, data, sizeof(data));
        bsp_i2c_write_nbyte(cfg->i2c_bus, cfg->dev_addr,
                            (uint8_t*)g_cmd_oled_null, sizeof(g_cmd_oled_null));
#endif
    }
}

void oled_clear(const oled_cfg_t *cfg, uint8_t x, uint8_t y, uint8_t x1, uint8_t y1) {
    if (g_oled_state)  return;
    g_oled_state = true;
    uint8_t data[] = {FUNC_CMD, 0xb0, 0x00, 0x10};
#if OS
    main_msgqueue_t msg;
#endif
    for (uint8_t i = y; i < y1; i++) {
        data[1] = 0xb0 + i;
        data[2] = (x & 0x0F);
        data[3] = ((x & 0xF0) >> 4) | 0x10;
#if OS
        msg.argv0 = cfg->dev_addr;
        msg.argv1 = 1;

        msg.event = MAIN_EVENT_I2C0_IOCTL;
        msg.data = data;
        msg.len = sizeof(data);
        main_enqueue(&msg);

        msg.event = MAIN_EVENT_I2C0_C_IOCTL;
        msg.data = (uint8_t*)g_cmd_oled_null;
        msg.len = x1 - x;
        main_enqueue(&msg);
#else
        bsp_i2c_write_nbyte(cfg->i2c_bus, cfg->dev_addr, data, sizeof(data));
        bsp_i2c_write_nbyte(cfg->i2c_bus, cfg->dev_addr,
                            (uint8_t*)g_cmd_oled_null, x1 - x);
#endif
    }
    g_oled_state = false;
}

uint8_t oled_open_display(const oled_cfg_t *cfg) {
#if OS==1
    main_msgqueue_t msg;
    msg.event = MAIN_EVENT_I2C0_C_IOCTL;
    msg.argv0 = cfg->dev_addr;
    msg.argv1 = 1;
    msg.data = (uint8_t*)g_cmd_oled_open_display;
    msg.len = sizeof(g_cmd_oled_open_display);
    return main_enqueue(&msg);
#else
    return bsp_i2c_write_nbyte(cfg->i2c_bus, cfg->dev_addr,
                               (uint8_t*)g_cmd_oled_open_display, sizeof(g_cmd_oled_open_display));
#endif
}

uint8_t oled_close_display(const oled_cfg_t *cfg) {
#if OS
    main_msgqueue_t msg;
    msg.event = MAIN_EVENT_I2C0_C_IOCTL;
    msg.argv0 = cfg->dev_addr;
    msg.argv1 = 1;
    msg.data = (uint8_t*)g_cmd_oled_open_display;
    msg.len = sizeof(g_cmd_oled_open_display);
    return main_enqueue(&msg);
#else
    return bsp_i2c_write_nbyte(cfg->i2c_bus, cfg->dev_addr,
                               (uint8_t*)g_cmd_oled_close_display, sizeof(g_cmd_oled_close_display));
#endif
}

/**
 * @brief  设置光标位置
 * @param  *cfg: 屏幕参数
 * @param  x: x新坐标
 * @param  y: y新坐标
 * @retval 0--成功 1--失败 2--BUSY
 */
uint8_t oled_set_pos(const oled_cfg_t *cfg, uint8_t x, uint8_t y) {
    uint8_t data[] = {FUNC_CMD, 0xb0, 0x00, 0x00};
    data[1] += y;
    data[2] = ((x & 0xF0) >> 4) | 0x10;
    data[3] = (x & 0x0F);
#if OS
    main_msgqueue_t msg;
    msg.event = MAIN_EVENT_I2C0_IOCTL;
    msg.argv0 = cfg->dev_addr;
    msg.argv1 = 1;
    msg.data = data;
    msg.len = sizeof(data);
    return main_enqueue(&msg);
#else

    return bsp_i2c_write_nbyte(cfg->i2c_bus, cfg->dev_addr, data, sizeof(data));
#endif
}

/**
 * @brief  写数据(带功能码)
 * @param  *cfg: 屏幕参数
 * @param  func: 功能码 cmd--0x00, data--0x40
 * @param  data: 数据指针， 首字节必须为0x40
 * @param  len: 数据长度
 * @retval 0--成功 1--失败 2--BUSY
 */
inline uint8_t oled_write_data(const oled_cfg_t *cfg, uint8_t func, uint8_t* data, uint16_t len) {
    uint8_t ret = 0;
    ret = bsp_i2c_write_nbyte_nostop(cfg->i2c_bus, cfg->dev_addr, &func, 1);
    if (ret != 0) return ret;

    ret = bsp_i2c_send_nbyte(cfg->i2c_bus, data, len);
    if (ret != 0) return ret;

    ret = bsp_i2c_stop(cfg->i2c_bus, 0);
    if (ret != 0) return ret;
    return ret;
}

/**
 * @brief  发送数据
 * @param  *cfg: 屏幕参数
 * @param  data: 数据指针， 首字节必须为0x40
 * @param  len: 数据长度
 * @retval 0--成功 1--失败 2--BUSY
 */
inline uint8_t oled_send_data(const oled_cfg_t *cfg, uint8_t* data, uint16_t len) {
#if OS
    main_msgqueue_t msg;
    msg.event = MAIN_EVENT_I2C0_IOCTL;
    msg.argv0 = cfg->dev_addr;
    msg.argv1 = 1;
    msg.data = data;
    msg.len = sizeof(data);
    return main_enqueue(&msg);
#else
    return bsp_i2c_write_nbyte(cfg->i2c_bus, cfg->dev_addr, data, len);
#endif
}
/* Private function prototypes -----------------------------------------------*/
